package com.iteaj.iot.taos;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.expression.MapAccessor;
import org.springframework.dao.DataAccessException;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.PropertyAccessor;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.SqlParameterValue;
import org.springframework.util.CollectionUtils;

import java.util.*;

public class SqlContext {

    private TaosSqlMeta meta;

    private static final String Insert = "insert into ";
    private static PropertyAccessor accessor = new MapAccessor();
    private static ExpressionParser parser = new SpelExpressionParser();
    private static Logger logger = LoggerFactory.getLogger(SqlContext.class);

    public SqlContext(TaosSqlMeta meta) {
        this.meta = meta;
    }

    /**
     * 批量任务
     * @return
     */
    public SqlExecContext getExecContext(Collection<EntitySql> execEntitySqls) {
        if (CollectionUtils.isEmpty(execEntitySqls)) {
            return null;
        }

        StringBuilder sb = new StringBuilder(Insert);
        TaosSqlMeta meta = this.getMeta();

        Map<String, List<EntitySql>> stringListMap = new HashMap<>();
        List<SqlParameterValue> parameterValues = new ArrayList<>();

        execEntitySqls.forEach(item -> {
            List<EntitySql> entitySqls = stringListMap.get(item.getTableName());
            if (entitySqls == null) {
                stringListMap.put(item.getTableName(), entitySqls = new ArrayList<>());
            }

            entitySqls.add(item);
        });

        stringListMap.forEach((tableName, entitySqls) -> {
            sb.append(tableName);
            if (meta.getTagInsertSql() != null) {
                sb.append(" ").append(meta.getTagInsertSql()).append(" tags ").append(meta.getTagParamSql());
                List<SqlParameterValue> tagParams = meta.getTagParams(tableName);
                parameterValues.addAll(tagParams);
            }

            sb.append(" ").append(meta.getInsertSql()).append(" values ");
            entitySqls.forEach(item -> {
                sb.append(meta.getParamSql()).append(" ");
                parameterValues.addAll(item.getValues());
            });
        });

        return new SqlExecContext(sb, parameterValues.stream().toArray(SqlParameterValue[]::new));
    }

    /**
     * 单条执行
     * @param entity
     * @return
     */
    public SqlExecContext getExecContext(Object entity) {
        final String tableName = this.getTableName(entity);
        StringBuilder sb = new StringBuilder(Insert).append(tableName).append(" ");

        List<SqlParameterValue> parameterValues = new ArrayList<>();
        if(this.getMeta().isUsing()) { // 数据表不存在自动创建
            if(!CollectionUtils.isEmpty(this.getMeta().getTags())) { // 使用tags创建
                sb.append(meta.getTagInsertSql()).append(" tags ").append(meta.getTagParamSql());
                parameterValues.addAll(meta.getTagParams(tableName));
            }
        }

        sb.append(" ").append(meta.getInsertSql()).append(" values ").append(meta.getParamSql());
        parameterValues.addAll(this.meta.getParams(entity));

        // 默认执行1条
        return new SqlExecContext(sb, parameterValues.stream().toArray(SqlParameterValue[]::new));
    }

    public String getTableName(Object entity) {
        // 获取数据表名
        String tableName = getMeta().getDataTableName();

        try {
            if(entity instanceof Map) {
                StandardEvaluationContext context = new StandardEvaluationContext(entity);
                context.addPropertyAccessor(accessor);
                return parser.parseExpression(tableName).getValue(context, String.class);
            } else {
                // 解析数据表表名
                return parser.parseExpression(tableName).getValue(entity, String.class);
            }
        } catch (Exception e) {
            throw new TaosException("解析数据表名失败["+tableName+"], 解析上下文对象["+entity+"]");
        }
    }

    public int update(Object entity, JdbcTemplate jdbcTemplate) {
        return this.doUpdate(this.getExecContext(entity), jdbcTemplate, 1);
    }

    public int update(List<Object> entities, JdbcTemplate jdbcTemplate) {
        Collection<EntitySql> entitySqls = new ArrayList<>(entities.size());
        entities.forEach(entity -> {
            String value = this.getTableName(entity);

            // 解析普通字段参数
            List<SqlParameterValue> params = this.getMeta().getParams(entity);

            entitySqls.add(new EntitySql(value, entity, params));
        });

        return this.doUpdate(this.getExecContext(entitySqls), jdbcTemplate, entities.size());
    }

    protected int doUpdate(SqlExecContext context, JdbcTemplate jdbcTemplate, int size) throws TaosException {
        int i;
        long start = System.currentTimeMillis();
        try {
            i = jdbcTemplate.update(context.getSql().toString(), context.getValues());
            writeLogger(context, null, start, size);
        } catch (DataAccessException e) {
            this.writeLogger(context, e, start, size);
            throw new TaosException(e);
        }

        return i;
    }

    private void writeLogger(SqlExecContext execContext, Throwable cause, long start, int size) {
        if (logger.isTraceEnabled()) {
            StringBuilder sb = new StringBuilder();
            for (SqlParameterValue value : execContext.getValues()) {
                sb.append(value.getValue()).append(", ");
            }

            logger.trace("TAOS适配 数据入库({}ms) - 条数：{}\r\n\t    Sql：{} \r\n\t params：{}"
                    , System.currentTimeMillis() - start, size, execContext.getSql().toString()
                    , sb.substring(0, sb.length() - 2), cause);
        }
    }

    public TaosSqlMeta getMeta() {
        return meta;
    }

    public void setMeta(TaosSqlMeta meta) {
        this.meta = meta;
    }

}
